﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using Nwc.XmlRpc;
using HttpServer;
using ExtensionLoader;

namespace WorldServer.Extensions
{
    public class XmlRpc : IExtension<WorldServer>, IXmlRpcProvider
    {
        // XML-RPC error codes, from http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
        const int METHOD_NOT_FOUND = -32601;
        const int INTERNAL_ERROR = -32603;

        WorldServer server;
        HashSet<string> paths = new HashSet<string>();
        Dictionary<string, XmlRpcMethod> callbacks = new Dictionary<string, XmlRpcMethod>();
        XmlRpcRequestDeserializer deserializer = new XmlRpcRequestDeserializer();

        public XmlRpc()
        {
        }

        public bool Start(WorldServer server)
        {
            this.server = server;

            return true;
        }

        public void Stop()
        {
        }

        public void AddXmlRpcHandler(string path, string methodName, XmlRpcMethod method)
        {
            callbacks[methodName] = method;

            if (paths.Add(path))
            {
                server.HttpServer.AddHandler("POST", "text/xml", path, XmlRpcHandler);
                server.HttpServer.AddHandler("POST", "application/x-www-form-urlencoded", path, XmlRpcHandler);
            }
        }

        private void XmlRpcHandler(IHttpClientContext client, IHttpRequest request, IHttpResponse response)
        {
            XmlRpcRequest rpcRequest = null;
            XmlRpcResponse rpcResponse = null;

            try
            { rpcRequest = deserializer.Deserialize(new StreamReader(request.Body)) as XmlRpcRequest; }
            catch (SystemException ex)
            { Logger.Warn("Failed to deserialize incoming XML-RPC request: " + ex.Message); }

            if (rpcRequest != null)
            {
                response.ContentType = "text/xml";
                response.Encoding = Encoding.UTF8;
                response.Chunked = false;

                XmlRpcMethod method;
                if (callbacks.TryGetValue(rpcRequest.MethodName, out method))
                {
                    rpcRequest.Params.Add(request.RemoteEndPoint);
                    rpcRequest.Params.Add(request.Uri);

                    try
                    {
                        rpcResponse = method(rpcRequest, request);

                        string responseString = XmlRpcResponseSerializer.Singleton.Serialize(rpcResponse);
                        byte[] buffer = Encoding.UTF8.GetBytes(responseString);
                        // Set the content-length, otherwise the LL viewer freaks out
                        response.ContentLength = buffer.Length;
                        response.Body.Write(buffer, 0, buffer.Length);
                        response.Body.Flush();
                    }
                    catch (Exception ex)
                    {
                        Logger.ErrorFormat("XML-RPC method [{0}] threw exception: {1}", rpcRequest.MethodName, ex);

                        rpcResponse = new XmlRpcResponse();
                        rpcResponse.SetFault(INTERNAL_ERROR, String.Format("Requested method [{0}] threw exception: {1}", rpcRequest.MethodName, ex.Message));
                        XmlRpcResponseSerializer.Singleton.Serialize(new XmlTextWriter(response.Body, Encoding.UTF8), rpcResponse);
                    }
                }
                else
                {
                    Logger.WarnFormat("XML-RPC method [{0}] not found", rpcRequest.MethodName);

                    rpcResponse = new XmlRpcResponse();
                    rpcResponse.SetFault(METHOD_NOT_FOUND, String.Format("Requested method [{0}] not found", rpcRequest.MethodName));
                    XmlRpcResponseSerializer.Singleton.Serialize(new XmlTextWriter(response.Body, Encoding.UTF8), rpcResponse);
                }
            }
            else
            {
                Logger.Warn("Bad XML-RPC request");
                response.Status = HttpStatusCode.BadRequest;
            }
        }
    }
}
